package com.example.netty.lengthfield.server;

import com.example.netty.common.server.ConnectionCountHandler;
import com.example.netty.common.server.ServerHeartBeatServerHandler;
import com.example.netty.common.utils.NettyConstants;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;

import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

/**
 * <p></p>
 *
 * @author xin
 * @version 2023/11/2 16:04
 **/
@SuppressWarnings("all")
public class LengthFieldServerInitializer extends ChannelInitializer<SocketChannel> {
    private static final String ENCODER = "encoder";
    private static final String DECODER = "decoder";
    /**
     * 长度字段偏移位置为0表示从包的第一个字节开始读取；
     * 此处表示从头开始的第10个字节开始计算长度 长度域从第几个字节开始
     */
    private final int lengthFieldOffset = 5;
    /**
     * 长度字段长为4，从包的开始位置往后4个字节的长度为长度字段； 长度域占了几个字节
     */
    private final int lengthFieldLength = 4;
    /**
     * 解析时候跳过多少个长度；长度域的偏移补偿
     */
    private final int lengthAdjustment = -5;
    /**
     * 解码出一个数据包之后，跳过前面的几个字节
     */
    private final int initialBytesToStrip = 0;
    /**
     * 如果failFast=true，当超过maxLength后会立刻抛出TooLongFrameException，不再进行解码；
     * 如果failFast=false，那么会等到解码出一个完整的消息后才会抛出TooLongFrameException
     * 则表示读取到长度域，他的值的超过maxFrameLength，就抛出一个 TooLongFrameException，而为false表示只有当真正读取完长度域的值表示的字节之后，才会抛出 TooLongFrameException，默认情况下设置为true，建议不要修改，否则可能会造成内存溢出。
     */
    private final boolean failFast = false;
    /**
     * 为读超时时间（即多长时间没有接受到客户端发送数据）
     */
    private final long readerIdleTime = 0;
    /**
     * 为写超时时间（即多长时间没有向客户端发送数据）
     */
    private final long writerIdleTime = 0;
    /**
     * 所有类型（读或写）的超时时间
     */
    private final long allIdleTime = 0;

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(new IdleStateHandler(readerIdleTime, writerIdleTime, allIdleTime, TimeUnit.SECONDS))
                // 优先判断最大长度
                // stripDelimiter:解码后的消息是否去除分隔符
                // failFast = false, 那么会等到解码出完整消息才会抛出 TooLongException。
//                ByteOrder.LITTLE_ENDIAN 解析长度的时候用小端，默认是大端
                .addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, Integer.MAX_VALUE,
                        5, 4, -5, 0, false))
                // 给客户端发送的时候 会默认的给 头部加 4 个字节，加上 当前包的长度
                .addLast(new LengthFieldPrepender(4))
                .addLast(ENCODER, new StringEncoder())
                // 与发送端有关系
                // 接收端需要 转成何总格式，与 channelRead 相关，此处是将 ByteBuf 转成byte[]
                .addLast(DECODER, new ByteArrayDecoder())
                .addLast(new ConnectionCountHandler())
                .addLast(new LengthFieldServerHandler())
                .addLast(new ServerHeartBeatServerHandler())
        ;

    }
}
